import org.json.JSONArray;
import org.json.JSONObject;

import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class FileHoldingModule {
    //参数: 当前类路径下的文件的文件名
    public static String from_file_to_string(String filename) {
        //获取类路径
        String path = FileHoldingModule.class.getResource("/").getPath();
        String jsonStr = "";
        try {
            File file = new File(path + filename);
            FileReader fileReader = new FileReader(file);

            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "Utf-8"));
            char[] char_buffer = new char[2048];
            int len = 0;
            StringBuffer sb = new StringBuffer();
            while ((len = br.read(char_buffer)) != -1) {
                sb.append(char_buffer, 0, len);
            }

            br.close();
            jsonStr = sb.toString();
            return jsonStr;
        } catch (Exception e) {
            return null;
        }
    }

    //参数: filename当前类路径下的文件的文件名 str为要写入此文件的字符串
    public static boolean from_string_to_file(String str, String filename) {
        String path = FileHoldingModule.class.getResource("/").getPath();
        File file = new File(path + filename);
        try {
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "Utf-8"));
            //new FileOutputStream(file,true)如果构造方法多了一个参数true,表示在文件末尾追加写入
            bw.write(str);
            bw.flush();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return false;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return false;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}


//"全网文件片持有表"
//功能:
//作为"请求调度算法" "接收方调度" 的一部分, 哪些人有哪些文件片,我要去向他们要!!!
//线程安全: 此类所有成员为private, 所有getter和setter方法都会给this对象加锁
class FileHoldingListPublic {
    private String file_name;
    private String whole_md5;
    private FCL_P_item[] file_chunk_list;

    synchronized public String getFile_name() {
        return file_name;
    }

    synchronized public void setFile_name(String file_name) {
        this.file_name = file_name;
    }

    synchronized public String getWhole_md5() {
        return whole_md5;
    }

    synchronized public void setWhole_md5(String whole_md5) {
        this.whole_md5 = whole_md5;
    }

    synchronized public FCL_P_item[] getFile_chunk_list() {
        return file_chunk_list;
    }

    synchronized public void setFile_chunk_list(FCL_P_item[] file_chunk_list) {
        this.file_chunk_list = file_chunk_list;
    }

    //合并函数
    //功能: 将一个 "FileHoldingListSelf对象" 合并到调用此函数的 "FileHoldingListPublic对象"
    //参数: FileHoldingListSelf对象
    //返回值: 合并成功返回true
    //为何此函数不给"this"加锁?因为此函数内采用了更细粒度的锁,无需对整个对象加锁
    boolean merge(FileHoldingListSelf FHL_S, String ip_str) {
        //判断 "全网文件持有表" 和 "自己的文件持有表" 是否为 "同一文件" 的文件持有表
        if (this.getWhole_md5() == FHL_S.getFile_whole_md5()) {
            //如果是,则允许合并
            FCL_S_item[] FHL_S_file_chunk_list = FHL_S.getFile_chunk_list();
            for (int i = 0; i < this.getFile_chunk_list().length; i++) {
                //如果这个客户端表示, 它持有这个文件片,即isIs_hold()为true
                if (FHL_S_file_chunk_list[i].isIs_hold()) {
                    //获取 这个文件片的"所有持有者ip表" -->
                    CopyOnWriteArraySet<String> holder_set = this.getFile_chunk_list()[i].getHolder_set();
                    holder_set.add(ip_str);//向 "所有持有者ip表" 中加入 "持有者的ip"
                }
            }
        } else {
            //如果不指向同一文件,则不允许合并
            System.out.println("不是指向同一文件,不允许合并");
            return false;
        }
        return true;
    }

    //构造函数
    //功能: 利用一个 "FileHoldingListSelf对象" 新建一个 "FileHoldingListPublic对象"
    FileHoldingListPublic(FileHoldingListSelf FHL_S, String ip_str) {
        this.file_name = FHL_S.getFile_name();
        this.whole_md5 = FHL_S.getFile_whole_md5();
        this.file_chunk_list = new FCL_P_item[FHL_S.getFile_chunk_list().length];
        //向file_chunk_list里填充数据
        FCL_S_item[] FHL_S_file_chunk_list = FHL_S.getFile_chunk_list();

        for (int i = 0; i < this.getFile_chunk_list().length; i++) {
            this.file_chunk_list[i] = new FCL_P_item();//先新建FCL_P_item对象(否则数组只存引用)
            this.file_chunk_list[i].setI(i);
            this.file_chunk_list[i].setMd5(FHL_S_file_chunk_list[i].getMd5());//getMd5获得一把锁,setMd5获得另一个对象的另一把锁,但是这里不会死锁!!思考一下为啥
            this.file_chunk_list[i].setHolder_set(new CopyOnWriteArraySet<String>());//新建 文件片"所有持有者ip表"
            this.file_chunk_list[i].getHolder_set().add(ip_str);//向 "所有持有者ip表" 中加入 "持有者的ip"
        }
    }
}

//线程安全: 此类所有成员为private, 所有getter和setter方法都会给this对象加锁
class FCL_P_item {//File Chunk List public item
    private int i;
    private String md5;
    private boolean is_response;//这个文件片的索要请求是否被另一个客户端愿意发送
    private CopyOnWriteArraySet<String> holder_set;//这个使用HashMap是方便查找,使用list遍历查找ip太慢了~~(毕竟此表内元素需要被删除)

    synchronized public boolean isIs_response() {
        return is_response;
    }

    synchronized public void setIs_response(boolean is_response) {
        this.is_response = is_response;
    }

    synchronized public int getI() {
        return i;
    }

    synchronized public void setI(int i) {
        this.i = i;
    }

    synchronized public String getMd5() {
        return md5;
    }

    synchronized public void setMd5(String md5) {
        this.md5 = md5;
    }

    synchronized public CopyOnWriteArraySet<String> getHolder_set() {
        return holder_set;
    }

    synchronized public void setHolder_set(CopyOnWriteArraySet<String> holder_set) {
        this.holder_set = holder_set;
    }
}

//"全网文件片持有表"管理map
//线程安全: 单例
//参数: key:整个文件的md5值 value:FileHoldingListPublic"全网文件片持有表"
//功能: 1.管理 所有 被传输文件 的 "全网文件片持有表"
//2.指定此时,本客户端正在"接收"(索要)的文件是哪一个
class FileHoldingListPublicMap {
    private static String now_receiving_file_md5;
    private static Map<String, FileHoldingListPublic> file_hold_list_public_map;
    private static final FileHoldingListPublicMap INSTANCE = new FileHoldingListPublicMap();

    private FileHoldingListPublicMap() {
        System.out.println("\"全网文件片持有表\"管理map 初始化");
        file_hold_list_public_map = new ConcurrentHashMap<>();
    }

    public static Map<String, FileHoldingListPublic> getMap() {
        return file_hold_list_public_map;
    }

    synchronized public static String getNow_receiving_file_md5() {
        return now_receiving_file_md5;
    }

    synchronized public static void setNow_receiving_file_md5(String now_receiving_file_md5) {
        FileHoldingListPublicMap.now_receiving_file_md5 = now_receiving_file_md5;
    }
}

//"自己的文件持有表"
//功能:
//作为"请求调度算法" "发送方调度" 的一部分, 我有哪些文件片, 我能接收谁的索要??
//函数列表:
//(1)FileHoldingListSelf 转换为 json字符串
//(2)json字符串 转换为 FileHoldingListSelf
//线程安全性: FileHoldingListSelf对象 被放入 "自己的文件持有表管理map"后
// 同一个对象可能会被 "RcvFileWriteThread接收文件片线程" 写入(接收到文件片后,更新自己的文件片持有表)
// ,会被 "FCL_S_receiver"自己的文件片持有表"接收线程" 读取
// ,会在程序启动的时候被创建  放入"管理map"
// ,会在接收到之前未见过的 "自己的文件片持有表" 时被创建  放入"管理map"
//此类所有成员为private, 所有getter和setter方法都会给this对象加锁
//同时此对象内的数组的数组元素的 所有成员为private, 所有getter和setter方法都会给this对象加锁
class FileHoldingListSelf {
    private String file_name;
    private String file_whole_md5;
    private boolean is_total_rcv = false;
    private FCL_S_item[] file_chunk_list;

    synchronized public String getFile_name() {
        return file_name;
    }

    synchronized public void setFile_name(String file_name) {
        this.file_name = file_name;
    }

    synchronized public String getFile_whole_md5() {
        return file_whole_md5;
    }

    synchronized public void setFile_whole_md5(String file_whole_md5) {
        this.file_whole_md5 = file_whole_md5;
    }

    synchronized public boolean isIs_total_rcv() {
        return is_total_rcv;
    }

    synchronized public void setIs_total_rcv(boolean is_total_rcv) {
        this.is_total_rcv = is_total_rcv;
    }

    synchronized public FCL_S_item[] getFile_chunk_list() {
        return file_chunk_list;
    }

    synchronized public void setFile_chunk_list(FCL_S_item[] file_chunk_list) {
        this.file_chunk_list = file_chunk_list;
    }

    //如果有字段为null,则直接置为null,不会报错
    //比如file_chunk_list允许为null
    //功能: 把FileHoldingListSelf转换为JSONObject
    synchronized JSONObject toJSON() {
        JSONObject result = new JSONObject();
        result.put("file_name", file_name);
        result.put("file_whole_md5", file_whole_md5);
        result.put("is_total_rcv", is_total_rcv);

        JSONArray jsa = new JSONArray();//JSONObject加入JSONArray是深拷贝还是浅拷贝?

        //如果file_chunk_list.length == 0,则file_chunk_list在json文件为空数组
        for (int i = 0; i < file_chunk_list.length; i++) {
            JSONObject item = new JSONObject();
            item.put("i", file_chunk_list[i].getI());
            item.put("md5", file_chunk_list[i].getMd5());
            item.put("is_hold", file_chunk_list[i].isIs_hold());
            item.put("addr", file_chunk_list[i].getAddr());

            jsa.put(item);
        }

        result.put("file_chunk_list", jsa);
        return result;
    }

    //功能: 把JSONObject转换为FileHoldingListSelf (转换为调用它的那个FileHoldingListSelf)
    synchronized void toFileHoldingListSelf(JSONObject jso) {
        file_name = (String) jso.get("file_name");
        file_whole_md5 = (String) jso.get("file_whole_md5");
        is_total_rcv = (boolean) jso.get("is_total_rcv");
        JSONArray jsa = jso.getJSONArray("file_chunk_list");

        FCL_S_item[] list = new FCL_S_item[jsa.length()];
        for (int i = 0; i < jsa.length(); i++) {
            FCL_S_item item = new FCL_S_item();
            JSONObject item_json = jsa.getJSONObject(i);
            item.setI(item_json.getInt("i"));
            item.setMd5(item_json.getString("md5"));
            item.setIs_hold(item_json.getBoolean("is_hold"));
            item.setAddr(item_json.getString("addr"));

            list[i] = item;
        }
        file_chunk_list = list;
    }

    //功能: 把JSONObject转换为FileHoldingListSelf (转换为调用它的那个FileHoldingListSelf)
    //这个函数是把 [别人的] FileHoldingListSelf作为"种子文件" 来初始化 [自己的] FileHoldingListSelf 调用的
    //特点: 所有文件片的"路径"字段设为null, is_hold字段设为false, is_total_rcv设为false
    synchronized void toFileHoldingListSelf_inital(JSONObject jso) {
        file_name = (String) jso.get("file_name");
        file_whole_md5 = (String) jso.get("file_whole_md5");
        is_total_rcv = false;
        JSONArray jsa = jso.getJSONArray("file_chunk_list");

        FCL_S_item[] list = new FCL_S_item[jsa.length()];
        for (int i = 0; i < jsa.length(); i++) {
            FCL_S_item item = new FCL_S_item();
            JSONObject item_json = jsa.getJSONObject(i);
            item.setI(item_json.getInt("i"));
            item.setMd5(item_json.getString("md5"));
            item.setIs_hold(false);
            item.setAddr("");//如果 item.is_hold==false,那么item.addr必定为空字符串

            list[i] = item;
        }
        file_chunk_list = list;
    }
}

//概述: 文件片的数据--是上面那个类的数组元素
//此类所有成员为private, 所有getter和setter方法都会给this对象加锁
class FCL_S_item {//File Chunk List self item
    private int i;
    private String md5;
    private boolean is_hold;
    private String addr;

    public int getI() {
        return i;
    }

    public void setI(int i) {
        this.i = i;
    }

    public String getMd5() {
        return md5;
    }

    public void setMd5(String md5) {
        this.md5 = md5;
    }

    public boolean isIs_hold() {
        return is_hold;
    }

    public void setIs_hold(boolean is_hold) {
        this.is_hold = is_hold;
    }

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    FCL_S_item() {
    }

    FCL_S_item(int i_, String md5_, boolean is_hold_, String addr_) {
        i = i_;
        md5 = md5_;
        is_hold = is_hold_;
        addr = addr_;
    }
}

//"自己的文件持有表"管理Map
//功能: 全局,单例,多线程读写
class FileHoldingListSelfMap {
    private static final FileHoldingListSelfMap INSTANCE = new FileHoldingListSelfMap();
    static Map<String, FileHoldingListSelf> file_hold_list_self_map;

    private FileHoldingListSelfMap() {
        System.out.println("FileHoldingListSelfMap'自己的文件持有表'管理Map 初始化");
        file_hold_list_self_map = new ConcurrentHashMap<>();
        //在这里开启线程扫描类路径下的所有json格式文件(这个线程不是循环的,只执行一次)
        new Thread(new FCL_S_Scanner(file_hold_list_self_map), "FCL_S_Scanner").start();
        //在这里开启发送 自己文件持有表 的线程(这个是一直执行的线程)
        new Thread(new FCL_S_Sender(file_hold_list_self_map), "FCL_S_Sender").start();
    }

    public static Map<String, FileHoldingListSelf> getMap() {
        return file_hold_list_self_map;
    }
}

//启动程序时,运行这个初始化"自己的文件持有表"管理Map的线程
//这里仅仅调用了FHLS_temp.toFileHoldingListSelf函数,此函数为线程安全的
class FCL_S_Scanner implements Runnable {
    Map<String, FileHoldingListSelf> file_hold_list_self_map;

    FCL_S_Scanner(Map<String, FileHoldingListSelf> file_hold_list_self_map_) {
        file_hold_list_self_map = file_hold_list_self_map_;
    }

    @Override
    public void run() {
        String path = FCL_S_Scanner.class.getResource("/").getPath() + File.separator;
        //STEP1 文件名的正则表达式(文件名全是数字)
        String filename_regular = ".+(.json|.JSON)$";//检测所有json后缀的文件
        //STEP2 列出路径中所有文件 file是一个路径
        File file_path = new File(path);
        if (!file_path.isDirectory()) {
            System.out.println("文件片文件夹不合法");
            return;
        }
        String name = file_path.getName();
        System.out.println(name);
        String[] list = file_path.list();//列出路径中所有文件  File.list()此方法的返回类型为File []，即，如果给定路径为目录，则返回文件路径中表示的文件数组，否则返回null。

        //STEP2 利用"Stream"的方法进行正则表达式匹配,筛选出符合正则表达式的文件名,存入List subFileNames
        Stream<String> stream = Arrays.stream(list);
        List<String> name_list = stream.filter(str -> {
            return str.matches(filename_regular);
        }).collect(Collectors.toList());

        //STEP4 把符合条件的文件名 变为FileHoldingModule对象 放进 "自己的文件持有表"管理Map
        for (String Filename : name_list) {
            System.out.println("添加持有表: " + Filename);
            String file_str = FileHoldingModule.from_file_to_string(Filename);
            FileHoldingListSelf FHLS_temp = new FileHoldingListSelf();
            FHLS_temp.toFileHoldingListSelf(new JSONObject(file_str));

            file_hold_list_self_map.put(FHLS_temp.getFile_whole_md5(), FHLS_temp);
        }
    }
}

//把 "自己的文件持有表" 发送给所有已连接的客户端
//启动时间: 此线程在 "自己的文件持有表"管理Map 的初始化函数 中开启,因为此线程对 file_hold_list_self_map 有依赖关系
class FCL_S_Sender implements Runnable {
    Map<String, FileHoldingListSelf> file_hold_list_self_map;

    FCL_S_Sender(Map<String, FileHoldingListSelf> file_hold_list_self_map_) {
        file_hold_list_self_map = file_hold_list_self_map_;
    }

    @Override
    public void run() {
        System.out.println("FCL_S_Sender\"自己的文件持有表\"发送线程 开始执行");
        while (true) {//每隔4秒,发送一次"自己的文件持有表"
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //遍历所有文件持有表
            for (Map.Entry<String, FileHoldingListSelf> entry : file_hold_list_self_map.entrySet()) {
                //这里对"管理map"里面的 FileHoldingListSelf对象 执行了读取操作,代码FHL_S_pack调用FHLS.toJSON(),会给FHLS对象加锁,线程安全
                byte[] chunk = MessageEncapsulationUtils.MessageToChunk(MessageEncapsulationUtils.FHL_S_pack(entry.getValue()));
                //遍历所有 控制报文发送队列
                Map<String, CtrlSendQueue> CtrlSendQueue_map = ControlConnectionModule.getInstance().SendQueue_map;
                for (Map.Entry<String, CtrlSendQueue> queue_entry : CtrlSendQueue_map.entrySet()) {
                    queue_entry.getValue().put(chunk);
                }
            }
        }

    }
}

//"自己的文件持有表"接收队列
//功能: 别的客户端发过来的 "自己的文件持有表" 暂存在这里
//单例
//存储内容: "自己的文件持有表"的字符串的字节数组(相当于直接从file里面读取出来的字节流)
class FCL_S_ReceiverQueue {
    static BlockingQueue<FCL_S_rcvqueue_item> receiver_queue;
    private static final FCL_S_ReceiverQueue INSTANCE = new FCL_S_ReceiverQueue();

    FCL_S_ReceiverQueue() {
        System.out.println("FCL_S_ReceiverQueue\"自己的文件持有表\"接收队列 已创建");
        receiver_queue = new LinkedBlockingQueue<>();
    }

    static BlockingQueue<FCL_S_rcvqueue_item> getQueue() {
        return receiver_queue;
    }

    static FCL_S_ReceiverQueue getInstance() {
        return INSTANCE;
    }
}

//FCL_S_ReceiverQueue"自己的文件持有表"接收队列 的 消息元素
//属性: 1.FCL_S报文 2.发送方的ip
//生成位置: 此报文由
class FCL_S_rcvqueue_item {
    byte[] FCL_S_bytearr;
    String ip;

    FCL_S_rcvqueue_item(byte[] b_a, String ip_) {
        FCL_S_bytearr = b_a;
        ip = ip_;
    }
}

//"自己的文件持有表"接收处理线程
//功能: 从FCL_S_ReceiverQueue里面拿出 [别的客户端发过来的 "自己的文件持有表"], 进行处理
class FCL_S_Receiver implements Runnable {
    BlockingQueue<FCL_S_rcvqueue_item> receiver_queue;//"文件片信息"接收队列
    Map<String, FileHoldingListSelf> file_hold_list_self_map;//"自己的文件片持有表"管理map
    Map<String, FileHoldingListPublic> file_hold_list_public_map;//"全网文件片持有表"管理map

    FCL_S_Receiver() {
        receiver_queue = FCL_S_ReceiverQueue.getQueue();
        file_hold_list_self_map = FileHoldingListSelfMap.getMap();
        file_hold_list_public_map = FileHoldingListPublicMap.getMap();
    }

    static {
        System.out.println("FCL_S_receiver'自己的文件持有表'接收处理线程 开始执行");
        new Thread(new FCL_S_Receiver(), "FCL_S_receiver").start();
    }

    @Override
    public void run() {
        while (true) {
            try {
                //由于FileHoldingListSelf对象的解包比较慢,所以放进这个线程里做
                FCL_S_rcvqueue_item message = receiver_queue.take();//从消息队列里拿出一个 其他客户端发来的 "自己的文件持有表"信息
                FileHoldingListSelf FHL_S_rev = MessageEncapsulationUtils.FHL_S_unpack(message.FCL_S_bytearr);

                String file_whole_md5 = FHL_S_rev.getFile_whole_md5();//刚刚接收到的 "自己的文件持有表" 指向的文件的md5值
                if (file_hold_list_self_map.containsKey(file_whole_md5) && file_hold_list_public_map.containsKey(file_whole_md5)) {
                    //如果"自己的文件片"管理map 和 "全网文件片管理map" 都存在这个 文件的"持有表信息" ,有如下3种可能
                    //上述描述等效为 -- file_hold_list_self_map 和 file_hold_list_public_map 里面 存在 这个md5值的 "FileHoldingListSelf对象"("自己的文件持有表"对象)
                    if (file_hold_list_self_map.get(file_whole_md5).isIs_total_rcv()) {
                        //可能1 此文件已经被完整接收到 -- 啥都不用做
                    } else {
                        //可能2 此文件正在被接收 -- 更新此文件的"全网文件片持有表"
                        //可能3 此文件虽然在管理map中,但是现在并不想接收这个文件 -- 更新此文件的"全网文件片持有表"
                        FileHoldingListPublic FHL_P_temp = file_hold_list_public_map.get(file_whole_md5);
                        FHL_P_temp.merge(FHL_S_rev, message.ip);
                    }
                } else {
                    //如果此 "FileHoldingListSelf对象" 不存在于 管理Map中
                    //可能1 此文件必定未被接收 --> 把这个 "FHLS"的"已被接收"字段改为false 并存入"管理map",并把这个"FHLS"存入本地json文件
                    FileHoldingListSelf FHL_S_rcv_new = MessageEncapsulationUtils.FHL_S_unpack_init(message.FCL_S_bytearr);//使用"init"接收方式
                    file_hold_list_self_map.put(file_whole_md5, FHL_S_rcv_new);
                    FileHoldingModule.from_string_to_file(FHL_S_rcv_new.toJSON().toString(), FHL_S_rcv_new.getFile_name() + ".json");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}